package jp.test;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Calendar;
import java.util.Random;
import java.util.Vector;



import jp.lab2.AVReceive3;

import org.cybergarage.http.HTTP;
import org.cybergarage.http.HTTPResponse;
import org.cybergarage.net.HostInterface;
import org.cybergarage.soap.SOAP;
import org.cybergarage.soap.SOAPResponse;
import org.cybergarage.upnp.Action;
import org.cybergarage.upnp.Argument;
import org.cybergarage.upnp.ArgumentList;
import org.cybergarage.upnp.ControlPoint;
import org.cybergarage.upnp.Device;
import org.cybergarage.upnp.DeviceList;
import org.cybergarage.upnp.Service;
import org.cybergarage.upnp.UPnP;
import org.cybergarage.upnp.control.ActionRequest;
import org.cybergarage.upnp.control.ActionResponse;
import org.cybergarage.upnp.device.NotifyListener;
import org.cybergarage.upnp.device.SearchResponseListener;
import org.cybergarage.upnp.event.EventListener;
import org.cybergarage.upnp.event.Subscription;
import org.cybergarage.upnp.event.SubscriptionMUServerList;
import org.cybergarage.upnp.event.SubscriptionRequest;
import org.cybergarage.upnp.event.SubscriptionResponse;
import org.cybergarage.upnp.ssdp.SSDP;
import org.cybergarage.upnp.ssdp.SSDPPacket;
import org.cybergarage.util.Debug;
import org.cybergarage.xml.Node;
import org.cybergarage.xml.NodeList;
import org.cybergarage.xml.Parser;
import org.geed.util.UPnPCipher;

import com.vladium.utils.SystemInformation;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;


public class AClient extends ControlPoint implements 
                 NotifyListener, EventListener, 
                 SearchResponseListener{
	
	/*
	 * ***********UPNP variable
	 */
	private final static String CLOCK_DEVICE_TYPE = "urn:schemas-upnp-org:device:clock:1";
	private final static String CLOCK_SERVICE_TYPE = "urn:schemas-upnp-org:service:timer:1";
	private final static String AIRCON_DEVICE_TYPE = "urn:schemas-upnp-org:device:aircon:1";
	private final static String WASHER_DEVICE_TYPE = "urn:schemas-upnp-org:device:washer:1";
	private final static String TV_DEVICE_TYPE = "urn:schemas-upnp-org:device:tv:1";
	
	private Device groupControllerDevice = null;
	private String deviceName = null;
	
	public final static int STATUS_INIT = 0;
	public final static int STATUS_REGISTERED = 1;
	public final static int STATUS_UNREGISTED = 2;
	
	private int clientStatus = STATUS_INIT;
	
	public void setStatus(int status){
		this.clientStatus = status;
	}
	
	public int getStatus(){
		return this.clientStatus;
	}
	
	
	/*
	 * ***********DEVICE SECURITY
	 */
	private String SID = "";
	private String sequenceBase = "";
	private byte[] remoteKeyInformation = null;
	
	private int keylen = 16;
	private int l = 128; //ramda
	
	private byte[][] secretBytes;
	private byte[][] publicIDBytes;
	
	private byte[] shareSecret;
	private byte[] sessionKey; //PKI
	
	private byte[] groupKey;
	private String currentGroupKeyID = "";
	
	private String keyfilePath = null;
	public int SecurityMode = UPnPCipher.UPnP_SECURITY_MODE0_DISABLE;
	private static final String GROUP_ID = "000-001";
	
	
	
	/*
	 * ***********TESTING
	 */
	private long startTime = 0; //millisec
	private long finishTime = 0;
	
	

	/*
	 * #########SECURITY SUPPORT FUNCTION#########
	 */
	public void setKeyFile(String keyfile){
		this.keyfilePath = keyfile;
	}
	
	private void readKeyFile(){
		try{
			// Read key bytes (8)
			FileInputStream in = new FileInputStream(keyfilePath);
			
		
			this.secretBytes = new byte[l+1][keylen];
			this.publicIDBytes = new byte[l+1][keylen];
			

			
			for(int i=0; i<l+1; i++){
				in.read(this.secretBytes[i], 0, this.secretBytes[i].length);				
			}
			
			
			for(int i=0; i<l+1; i++){
				in.read(this.publicIDBytes[i], 0, this.publicIDBytes[i].length);
				//System.out.print(this.publicIDBytes[i]+" ");
			}
			
		}catch(IOException ioe){
			ioe.printStackTrace();
		}
		
	}
	
	private String readPublicStr(){
		//make 2 dimension arrays into one long array
    	byte[] tmpPubID = new byte[(l+1)*keylen];
    	int current = 0;
    	for(int i=0; i<l+1; i++){
    		System.arraycopy(publicIDBytes[i], 0, tmpPubID, current, keylen);
    		current += keylen;
    	}
    	BASE64Encoder base64Enc = new BASE64Encoder();
    	return base64Enc.encode(tmpPubID);
	}
	
    private byte[] computeSecret(byte[] publicIDBytes){
		// see full verion at org.geed.kpd.test.TestKey
		
		byte[][] publicID = new byte[l+1][keylen];
		int current =0;
		for(int i=0; i<l+1; i++){
			System.arraycopy(publicIDBytes, current, publicID[i], 0, keylen);
			current += keylen;
		}
		
		
		
		byte[] result = new byte[keylen];
		for(int i=0; i<l+1; i++){
			byte[] tmp = new byte[keylen];
			
			for(int j=0; j<keylen; j++){
				tmp[j] = (byte)(this.secretBytes[i][j]*publicID[i][j]);
			}
			
			for(int j=0; j<keylen; j++){
				result[j] += tmp[j];
			}
			
			
		}
		
		//set secret bytes use to generate secret key
		return result;
	}
	
	
	/*
	 * ##############UPNP STANDARD FUNCTIONS##############
	 * - START
	 * - SEARCH RESPONSE LISTENNER
	 * - NOTIFY RESPONSE LISTENNER
	 * - CONTROL
	 */
	public AClient(/*int ssdp_port, int eventsub_port, */String keyfilePath){
		//super(ssdp_port	,eventsub_port);
		
		
		this.deviceName = keyfilePath;
		//System.out.println("keyfile path = "+keyfilePath);
		caCertificatePath = "CA\\"+keyfilePath+"_cert\\cacert.der";
		certificatePath = "CA\\"+keyfilePath+"_cert\\newcert.der";
		privateKeyPath = "CA\\"+keyfilePath+"_cert\\newkey.der";
		
		addNotifyListener(this);
		addSearchResponseListener(this);
		addEventListener(this);
		UPnP.setEnable(UPnP.USE_ONLY_IPV4_ADDR);
		
		//Debug.on();
		Debug.off();
		this.SecurityMode = UPnPCipher.UPnP_SECURITY_MODE1_KPS;
		
		//kpd
		setKeyFile(keyfilePath);
		readKeyFile();
		
		
		//String keyAStr = "thisaboothisaboo";
		//UPnPCipher.setKey(keyAStr.getBytes());
		//UPnPCipher.on();
		
		//UPnPCipher.onCentralize();
		//UPnPCipher.setHost("172.30.140.243");
		//UPnPCipher.setPort(4557);

	
	}
	
	public boolean searchClockDevice(){
		DeviceList devList = this.getDeviceList();

		this.groupControllerDevice = getDevice(CLOCK_DEVICE_TYPE);
		
		if(this.groupControllerDevice == null){
			System.err.println(deviceName+" dev = "+devList.size()+" search device ...");
			return false;
		}
		System.err.println(deviceName+" dev = "+devList.size()+" found device ...");
		return true;
	}
	
	public void setGroupControllerDevice(Device gc){
		if(gc != null){
			this.groupControllerDevice = gc;
		}
	}
	
	public void deviceNotifyReceived(SSDPPacket packet)
	{
		//System.out.println(packet.toString());
		
		if (packet.isDiscover() == true) {
			String st = packet.getST();
			String sg = packet.getSG();
			String gc = packet.getGC();
			//System.out.println(deviceName+" ssdp:discover : ST = " + st +" SG id = "+sg+" GC = "+gc); 
		}
		else if (packet.isAlive() == true) {
			String usn = packet.getUSN();
			String nt = packet.getNT();
			String url = packet.getLocation();
			String sg = packet.getSG();
			String gc = packet.getGC();
			byte[] dataBytes = packet.getDatagramPacket().getData();
			//String dataStr = new String(dataBytes, 0, dataBytes.length);
			//String data = packet.toString();
			
			//System.out.println("ssdp:alive : uuid = " + usn + ", NT = " + nt + ", location = " + url + " SG = "+sg+" gc = "+gc);
			//System.out.println("ssdp:alive : data = "+ dataStr);
		}
		else if (packet.isByeBye() == true) {
			String usn = packet.getUSN();
			String nt = packet.getNT();
			//System.out.println("ssdp:byebye : uuid = " + usn + ", NT = " + nt); 
		}
		
	}
	
	public void eventNotifyReceived(String uuid, long seq, String name, String value)
	{
		//System.out.println("event notify : uuid = " + uuid + ", seq = " + seq + ", name = " + name + ", value =" + value);
		
		//check if group key id is changed
		if(this.currentGroupKeyID.equals(value) == true){
			//System.out.println(this.SID+"************key already up-to-date kid="+value);
			return;
		}
		
		//update group key
		getGroupKey();

		//System.out.println(this.SID+"************update group key success kid="+value);
		
		//check if group key id is updated
	}
	
	public void deviceSearchResponseReceived(SSDPPacket packet)
	{
		String uuid = packet.getUSN();
		String st = packet.getST();
		String url = packet.getLocation();
		//System.out.println("device search res : uuid = " + uuid + ", ST = " + st + ", location = " + url);
	}
	
	public void getClockTime()
	{
	
		if(this.groupControllerDevice == null){
			System.err.println("GET TIME:NO DEVICE");
			return;
		}
		
		Action getTimeAct = groupControllerDevice.getAction("GetTime");
		
		if(getTimeAct.postControlAction() == false)
			return;
		
		ArgumentList outArgList = getTimeAct.getOutputArgumentList();
		String time = outArgList.getArgument(0).getValue();
		
		//System.out.println("Time is "+time);
		
	}
	
	/*
	 * ########EXTENDED DEVICE SECURITY FUNCTION###############
	 * - START KPS SECURITY MODE
	 * - EXCHANGE KEY INFORMATION
	 * - LIFETIME SEQUENCE BASE
	 * - SUBSCRIBE GROUP KEY ID CHANGE
	 * - RETRIEVE NEW GROUP KEY
	 */
	
	public boolean startKPSMode(){
		
		
		//start authen process
		long startTime = System.currentTimeMillis();
		//final SystemInformation.CPUUsageSnapshot m_prevSnapshot =
		//	SystemInformation.makeCPUUsageSnapshot ();
		
		getKeyinformation(); //step1
		getLifeTimeSequenceBase(); //step2
		
		//int retryCnt = 10;
		
		boolean rt = authenticate();
		/*
		while(authenticate() == false){ //step3
			retryCnt --;
			if(retryCnt == 0){
				return false;
			}
		}*/
		
		//finish authen process
		long finishTime = System.currentTimeMillis();
		//final SystemInformation.CPUUsageSnapshot event = 
		//	SystemInformation.makeCPUUsageSnapshot ();
		//double receivedCPUUsage = 100.0 * SystemInformation.getProcessCPUUsage (m_prevSnapshot, event);
		//System.out.println("Current CPU usage is "+receivedCPUUsage+"%");
		
		printESPTime(finishTime - startTime, "time\\authen_test-kps-salt-test0.log", deviceName);
		
		subScribeGroupKey(); // step 4 leaveRekey subscription
		
		//future work
		//subScribeMulticast(); //step 5 joinRekey subscription
		
		return rt;
	}
	
	public void getKeyinformation() //step1
	{
		
		if(this.groupControllerDevice == null){
			System.err.println("GET KEY INFO:NO DEVICE");
			return ;
		}
		
		Action getKeyInformation = groupControllerDevice.getAction("GetKeyInformation");
		Argument remoteKeyInformation = getKeyInformation.getArgument("RemoteKeyInformation");
		String remoteKeyInfo = readPublicStr();
		remoteKeyInformation.setValue(remoteKeyInfo);
		
		if(getKeyInformation.postControlAction() == false)
			return ;
		
		ArgumentList outArgList = getKeyInformation.getOutputArgumentList();
		String clockKeyInformation = outArgList.getArgument(0).getValue();
		
		
		//System.out.println("Clock Key information is "+clockKeyInformation);
		BASE64Decoder base64Dec = new BASE64Decoder();
		
		try{
			this.remoteKeyInformation = base64Dec.decodeBuffer(clockKeyInformation);
		}catch(IOException ioe){
			ioe.printStackTrace();
		}
	}
	
	private String caCertificatePath = null;
	private String certificatePath = null;
	private String privateKeyPath = null;
	
	//PKI support
	private Certificate caCert = null;
	private byte[] gcCertBytes = null;
	//private RSAPrivateKey privKey = null;
	
	public boolean startPKIMode(){
		
		
		//start authen process
		//long startTime = System.currentTimeMillis();
		
		getCertificate();
		getLifeTimeSequenceBase();
		
		boolean rt = authenticatePKI();
		
		//long finishTime = System.currentTimeMillis();
		
		//printESPTime(finishTime - startTime, "time\\authen_test-pki-salt-test0.log", deviceName);
		
		subScribeGroupKey();
		
		return rt;
		
		
	}
	
	public void getCertificate()
	{
		
		//check if GC already found
		if(this.groupControllerDevice == null){
			System.err.print("GET CERTIFICATE ERR: NO GC");
			return;
		}
		
		//load control point certificate
		byte[] certBytes = loadCertificate(certificatePath);
		BASE64Encoder base64Enc = new BASE64Encoder();
		String base64Cert = base64Enc.encode(certBytes);
		
		
		//set cp cert as agrument
		Action getCertificate = groupControllerDevice.getAction("GetCertificate");
		Argument certificateIn = getCertificate.getArgument("CertificateIn");
		certificateIn.setValue(base64Cert);
				
		//postaction control to get gc cert
		if(getCertificate.postControlAction() == false)
			return;
		
		//store gc cert
		try{
			//docode base64
			String certOut = getCertificate.getArgument("CertificateOut").getValue();
			BASE64Decoder base64Dec =new BASE64Decoder();
			byte[] gcCertBytes = base64Dec.decodeBuffer(certOut);
			/*
			ByteArrayInputStream bin = new ByteArrayInputStream(gcCertBytes);
			
			//generate certificate
			CertificateFactory cf = CertificateFactory.getInstance("X509");
			Certificate gcCert = cf.generateCertificate(bin);
			
			//CPU load #1
			//verify gc certificate
			loadCACertificate(caCertificatePath);
			gcCert.verify(caCert.getPublicKey());
			*/
			//save gc certificate
			this.gcCertBytes = gcCertBytes;
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	//load own certificate (will sent to network)
	private byte[] loadCertificate(String certPath){
		File certFile = new File(certPath);
		
		try{//read cacert file
			DataInputStream dis = new DataInputStream(new FileInputStream(certFile));
			CertificateFactory cf = CertificateFactory.getInstance("X509");
			Certificate cert = cf.generateCertificate(dis);
			
			
			return cert.getEncoded();
			
		}catch(Exception e){
			e.printStackTrace();
		}
		return null;
	}
	
	private byte[] loadPrivKey(String privKeyPath){
		//DER format
		File privKeyFile = new File(privKeyPath);
		
		try{
			//read private key DER file
			DataInputStream dis = new DataInputStream(new FileInputStream(privKeyFile));
			byte[] privKeyBytes = new byte[(int)privKeyFile.length()];
			dis.read(privKeyBytes);
			dis.close();
			
			return privKeyBytes;
			//KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			
			//decode private key
			//PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privKeyBytes);
			//RSAPrivateKey privKey = (RSAPrivateKey)keyFactory.generatePrivate(privSpec);
			
			//this.privKey = privKey;
			
			
		}catch(Exception e){
			e.printStackTrace();
		}
		
		return null;
	}
	
	private boolean loadCACertificate(String caCertPath){
		//der format
		if(this.caCert != null)
			return false;
		
		File cacertFile = new File(caCertPath);
		
		try{
			//read cacert file
			DataInputStream dis = new DataInputStream(new FileInputStream(cacertFile));
			CertificateFactory cf = CertificateFactory.getInstance("X509");
			Certificate cert = cf.generateCertificate(dis);
			this.caCert = cert;
			
			return true;
			
		}catch(Exception e){
			e.printStackTrace();
		}
		return false;
	}
	
	public void getLifeTimeSequenceBase() //step2
	{
	
		if(this.groupControllerDevice == null){
			System.out.println("LTSB :NO DEVICE");
			return;
		}
		
		Action getLifeTimeSequenceBaseAction = groupControllerDevice.getAction("GetLifetimeSequenceBase");
		if(getLifeTimeSequenceBaseAction.postControlAction() == false){
			return;
		}
		
		
		
		//arg 2
		String sequenceBase = getLifeTimeSequenceBaseAction.getArgument("SequenceBase").getValue();
		//System.out.println("Sequence Base value="+sequenceBase);
		
		this.sequenceBase = sequenceBase;
	}
	
	public boolean authenticate() // step 3
	{
		/*
		if(this.dev == null){
			System.err.println("AUTHEN :NO DEVICE");
			return false;
		}
		*/
		
		//System.out.println("Authenticate KPS Mode");
		
		//final SystemInformation.CPUUsageSnapshot m_prevSnapshot =
		//	SystemInformation.makeCPUUsageSnapshot ();
		
		//CPU load #1
		long startTime = System.nanoTime();
		byte[] secretByte = computeSecret(this.remoteKeyInformation);
		long finishTime = System.nanoTime();
		long espTime = finishTime - startTime;
		System.out.print("computeSecret_time "+espTime);
		
		this.shareSecret = secretByte;
		String salt = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012312345";
		String salt2 = "0123456789012345678901234567890123456789012345678901234567890123456789";
		
		//System.out.println("SequenceBase = "+this.sequenceBase);
		//arg 3
		//String encSequenceBase = UPnPCipher.encode(secretByte,sequenceBase);
		//change to H(LTSB+Secret)
		String secretStr = null;
		try{
			secretStr = new String(secretByte,"UTF-8");
		}catch(Exception e){}
		//System.out.println("SecretKey Bytes = "+secretStr);
		String toHashed = this.sequenceBase+secretStr+salt+salt2;
		//System.out.println("hash length = "+toHashed.length());
		String authSecret = null;
		
		startTime = System.nanoTime();
		//digest message
		try{
			MessageDigest algorithm = MessageDigest.getInstance("SHA1");
			algorithm.reset();
			algorithm.update(toHashed.getBytes("UTF-8"));
			//CPU load #2
			byte[] b = algorithm.digest();
			authSecret = new BASE64Encoder().encode(b);
			
		}catch(Exception e){
			e.printStackTrace();
		}
		finishTime = System.nanoTime();
		espTime = finishTime -startTime;
		System.out.print(" digest_time "+espTime);
		
		
		
		//go connect to device
		Action authenAct = groupControllerDevice.getAction("Authenticate");
		
		//Argument shareKeyID = authenAct.getArgument("SID");
		//shareKeyID.setValue(SID);
		
		Argument seqArgument = authenAct.getArgument("SequenceBase");
		seqArgument.setValue(sequenceBase);
		
		Argument authArgument = authenAct.getArgument("AuthSecret");
		authArgument.setValue(authSecret);
		
		//out arg
		if(authenAct.postControlAction() == false){
			return false;
		}
		
		Argument resultArg = authenAct.getArgument("Result");
		String result = resultArg.getValue();
		
		String SID = authenAct.getArgumentValue("SID");
		this.SID = SID;
		
		String groupkeyEncoded = authenAct.getArgumentValue("GroupKeyEncoded");
		String groupkeyID = authenAct.getArgumentValue("GroupKeyID");
		
		startTime = System.nanoTime();
		//CPU load #3
		String groupKeyStr = UPnPCipher.decode(shareSecret,groupkeyEncoded);
		finishTime = System.nanoTime();
		espTime = finishTime - startTime;
		System.out.println(" decrypt_time "+espTime);
		
		try{
			this.groupKey = new BASE64Decoder().decodeBuffer(groupKeyStr);
		}catch(Exception e){
			e.printStackTrace();
		}
		
		//System.out.println("Authenticate result: groupkeyencoded===>"+groupkeyEncoded);
		//System.out.println("Authenticate result: groupkeyid====>"+groupkeyID);
		//System.out.println("Authenticate result: sid====>"+SID);
		
		if(result.equals("1") == true){
			//subScribeClock();
			
			
			//long finishTime = System.currentTimeMillis();
			//final SystemInformation.CPUUsageSnapshot event = 
			//	SystemInformation.makeCPUUsageSnapshot ();
			//double receivedCPUUsage = 100.0 * SystemInformation.getProcessCPUUsage (m_prevSnapshot, event);
			//System.out.println("CPUusage "+receivedCPUUsage+" %");
			
			return true;
		}
		
		return false;
	}
	
	public boolean authenticatePKI()
	{
		//final SystemInformation.CPUUsageSnapshot m_prevSnapshot =
		//	SystemInformation.makeCPUUsageSnapshot ();
		long startTime = 0;
		long finishTime = 0;
		long espTime = 0;
		
		try{
			ByteArrayInputStream bin = new ByteArrayInputStream(this.gcCertBytes);
			
			//generate certificate
			CertificateFactory cf = CertificateFactory.getInstance("X509");
			Certificate gcCert = cf.generateCertificate(bin);
			
			//CPU load #1
			//verify gc certificate
			loadCACertificate(caCertificatePath);
			
			startTime = System.nanoTime();
			gcCert.verify(this.caCert.getPublicKey());
			finishTime = System.nanoTime();
			espTime = finishTime - startTime;
			System.out.print("Verify_GC_Cert_time "+espTime);
		}catch(Exception e){
			e.printStackTrace();
		}
		
		
		String signedLTSB = "";
		String salt = "12345678901234567890123456789012345678901234567890123456789012345678901234567890123123459876543219876541";
		String salt2 = "0123456789012345678901234567890123456789012345678901234567890123456789";
		
		RSAPrivateKey privKey = null;
		byte[] privKeyBytes = loadPrivKey(this.privateKeyPath);
		
		String sigStr = this.sequenceBase+salt+salt2;
		//System.out.println("sigStr lenght = "+sigStr.length());
		//CPU load #2
		//sign LTSB
		startTime = System.nanoTime();
		try{
			//load private key
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privKeyBytes);
			privKey = (RSAPrivateKey) keyFactory.generatePrivate(privSpec);
			
			
			Signature sign = Signature.getInstance("SHA1WithRSA");
			sign.initSign(privKey);
			sign.update(sigStr.getBytes());
			byte[] signedLTSBBytes = sign.sign();
			signedLTSB = new BASE64Encoder().encode(signedLTSBBytes);
		}catch(Exception e){
			e.printStackTrace();
		}
		
		finishTime = System.nanoTime();
		espTime = finishTime - startTime;
		System.out.print(" sign_time "+espTime);
		
		
		
		//send request
		Action authenAct = groupControllerDevice.getAction("AuthenticatePKI");
		authenAct.setArgumentValue("SequenceBase", this.sequenceBase);
		authenAct.setArgumentValue("AuthSecret", signedLTSB);
		
		if(authenAct.postControlAction() == false)
			return false;
		
		String result = authenAct.getArgumentValue("Result");
		
		String SID = authenAct.getArgumentValue("SID");
		this.SID = SID;
		
		String groupkeyEncoded = authenAct.getArgumentValue("GroupKeyEncoded");
		String groupkeyID = authenAct.getArgumentValue("GroupKeyID");
		
		String sessionKeyEncoded = authenAct.getArgumentValue("SessionKeyEncoded");
		
		
		//CPU load #3 #4
		startTime = System.nanoTime();
		String groupKeyStr = UPnPCipher.decodeRSA(privKeyBytes, groupkeyEncoded);
		String sessionKeyStr = UPnPCipher.decodeRSA(privKeyBytes, sessionKeyEncoded);
		finishTime = System.nanoTime();
		espTime = finishTime - startTime;
		
		System.out.println(" RSADecryption_time "+espTime);
		
		try{
			BASE64Decoder base64Decoder = new BASE64Decoder();
			this.groupKey = base64Decoder.decodeBuffer(groupKeyStr);
			this.sessionKey = base64Decoder.decodeBuffer(sessionKeyStr);
			this.shareSecret = this.sessionKey;
		}catch(Exception e){
			e.printStackTrace();
		}
		
		if(result.equals("1") == true){
			//subScribeGroupKey();
			
			//long finishTime = System.currentTimeMillis();
			//final SystemInformation.CPUUsageSnapshot event = 
				SystemInformation.makeCPUUsageSnapshot ();
			//double receivedCPUUsage = 100.0 * SystemInformation.getProcessCPUUsage (m_prevSnapshot, event);
			//System.out.println("CPUusage "+receivedCPUUsage+" %");
			
			return true;
		}
		
		return false;
	}

	public void subScribeGroupKey() //step 4
	{
	
		if(this.groupControllerDevice == null){
			//System.out.println("SUBSCRIBE:NO DEVICE");
			return;
		}
		
		
		Service deviceSecurityService = groupControllerDevice.getService("urn:schemas-upnp-org:service:devicesecurity:1");
		//System.out.println("===>Subscribing");
		boolean subRet = subscribe(deviceSecurityService);
		if(subRet == true){
			String sid = deviceSecurityService.getSID();
			long timeout = deviceSecurityService.getTimeout();
			
			//System.out.println("===>Subscribed success SID="+sid+" timeout="+timeout);
		}else{
			//System.out.println("===>Subscrib failed");
		}
	
	}
	
	private void subScribeMulticast(){
		////////////////////////////////////////
		// Multicast subscription
		////////////////////////////////////////
		
		SubscriptionMUServerList subMUServerList = new SubscriptionMUServerList();
		subMUServerList.open();
		subMUServerList.setControlPoint(this);
		subMUServerList.start();
	}
	
	
	
	public void secureSoapExecution(Action act) //step 5
	{
		
		if(this.groupControllerDevice == null){
			//System.out.println("DECRYPT AND EXECUTE:NO DEVICE");
			return ;
		}
		
		//1 action to secure exec
		//Action getTimeAct = dev.getAction("GetTime");
		
		ActionRequest actionRequest = new ActionRequest();
		actionRequest.setRequest(act, act.getInputArgumentList());
		//actionRequest.print();
		
		//2 original content
		String contentString = actionRequest.getHeader();
		contentString += HTTP.CRLF;
		contentString += actionRequest.getContentString();
		
		//3 encode content
		String contentEnc = UPnPCipher.encode(shareSecret, contentString);

		
		
		Action decryptAndExecuteAct = groupControllerDevice.getAction("DecryptAndExecute");
		Argument requestArgument = decryptAndExecuteAct.getArgument("Request");
		Argument sidArgument = decryptAndExecuteAct.getArgument("SID");
		
		sidArgument.setValue(this.SID);
		requestArgument.setValue(contentEnc);
		
		if(decryptAndExecuteAct.postControlAction() == false)
			return ;
		
		Argument resultArgument = decryptAndExecuteAct.getArgument("Result");
		String resultEnc = resultArgument.getValue();

		String result = UPnPCipher.decode(shareSecret, resultEnc);
		
		
		// process clock result
		HTTPResponse httpResponse = new HTTPResponse(new ByteArrayInputStream(result.getBytes()));
		SOAPResponse soapResp = new SOAPResponse(httpResponse);
		//soapResp.print();
		try{
			ByteArrayInputStream byteIn = new ByteArrayInputStream(httpResponse.getContent());
			Parser xmlParser = SOAP.getXMLParser();
			Node rootNode = xmlParser.parse(byteIn);
			soapResp.setEnvelopeNode(rootNode);
		}catch(Exception e){
			Debug.warning(e);
		}
		
		ActionResponse actionResp = new ActionResponse(soapResp);
		act.setControlResponse(actionResp);
		act.setStatus(actionResp.getStatusCode());
		act.getArgumentList().set(actionResp.getResponse());
		
		//return act;
		//String timeStr = getTimeAct.getArgument("CurrentTime").getValue();
		//System.out.println("Current time ===>"+timeStr);
		
		//System.out.println("Clock Key information is "+clockKeyInformation);
	}

	private int rekeyCnt = 1;
	public void getGroupKey() //step 6 (when rekey is needed)
	{
	
		if(this.groupControllerDevice == null){
			//System.out.println("GET GROUPKEY:NO DEVICE");
			return;
		}
		
		//System.out.println(deviceName+" rekey count "+rekeyCnt);
		rekeyCnt ++;
		
		Action getGroupKey = groupControllerDevice.getAction("GetGroupKey");
		secureSoapExecution(getGroupKey); // get groukey via secure soap
		
		String groupKey = getGroupKey.getArgumentValue("GroupKey");
		if(groupKey.equals("")== true){
			//System.out.println("GetGroupKey return FAILED");
			return;
		}
		//System.out.println("GroupKey ===>"+groupKey);
		
		BASE64Decoder base64Dec = new BASE64Decoder();
		try{
			this.groupKey = base64Dec.decodeBuffer(groupKey);
		}catch(Exception e){
			e.printStackTrace();
		}
		
	}
	
	public void leaveGroup() //step 7 (leave group)
	{
		unsubScribeClock();
	
		if(this.groupControllerDevice == null){
			//System.out.println("LEAVE GROUP:NO DEVICE");
			return;
		}
		
		Action leaveGroup = groupControllerDevice.getAction("LeaveGroup");
		leaveGroup.getArgument("SID").setValue(this.SID);
		secureSoapExecution(leaveGroup); // get groukey via secure soap
		
		String result = leaveGroup.getArgumentValue("Result");
		//System.out.println("********************************************LeaveGroup result ===>"+result);
		
		
	}

	public void unsubScribeClock() // unsubscribe before leavegroup
	{
	
		if(this.groupControllerDevice == null){
			//System.out.println("UNSUBSCRIBE:NO DEVICE");
			return;
		}
		
		
		Service deviceSecurityService = groupControllerDevice.getService("urn:schemas-upnp-org:service:devicesecurity:1");
		//System.out.println("===>Subscribing");
		boolean subRet = unsubscribe(deviceSecurityService);
		if(subRet == true){
			String sid = deviceSecurityService.getSID();
			long timeout = deviceSecurityService.getTimeout();
			
			//System.out.println("===>unsubscribed success SID="+sid+" timeout="+timeout);
		}else{
			//System.out.println("===>Subscrib failed");
		}
	
	}
	
	/*
	 * MAIN FOR TESTING PURPOSE
	 */
	public static void main(String args[]){
		/*
		 * Test PKI mode Sep 09
		 */

		// test new gc and sg function
		/*
		AClient a = new AClient(8008,8058,"secret\\m1.txt");
		a.start();
		while(a.getStatus() != AClient.STATUS_REGISTERED){
			a.printSecureSupportedDevicePKI();//authen too
			try{
				Thread.sleep(5000);
			}catch(Exception e){
				
			}
			
		}
		System.out.println("registered ");
		*/
		
		//x3 controlpoint
		//***** lastest on sep09
		int nodeNum = 20;
		int firstNode = 1;
		AClient[] aArr = new AClient[nodeNum];
		for(int i=0; i< nodeNum; i++){
			//aArr[i] = new AClient(8008+i,8058,"m"+firstNode); //pki
			aArr[i] = new AClient(/*8008+i,8058,*/"secret\\m"+firstNode+".txt"); //kpd
			aArr[i].start();
			
			firstNode++;
		
			while(aArr[i].getStatus() != AClient.STATUS_REGISTERED){
				//aArr[i].printSecureSupportedDevicePKI();//authen too
				aArr[i].printSecureSupportedDevice();//authen too
				
				try{
					Thread.sleep(5000+100*i);
				}catch(Exception e){
					
				}
			}
			//System.out.println("node "+(i+1)+" registered ");
		}
		
		
		/* test ##1 api
		AClient a = new AClient(args[0]);
		
		//AClient c = new AClient("secret\\m14.txt");

		a.start(); //start upnp and device security process
		
		boolean poweron = (args[1].equals("1")) ? true:false;
		
		//a.startAVReceiever(poweron);
		try{
			Thread.sleep(10000);
		}catch(Exception e){}
		
		
		
		
		int t=2000;
		while(t<100000){
			try{
				Thread.sleep(t);
			}catch(Exception e){}
			
			t += 2000;
			//do something unicast
			//a.securedGetClockTime();
			//a.startAVReceiever();
			
			//or do something multicast
			//b.securedGetClockTime();
		}
		*/
		/*
		try{
			Thread.sleep(5000);
		}catch(Exception e){}
		
		int fileNumber = 40;
		int node = 4;
		int ssdp_port = 8008;
		int eventsub_port = 8058;
		*/
		
		/* test #2 test rekey time */
		//set up number is start number of key file
		//       node is node number to run
		
		
		/*
		AClient[] aclient = new AClient[node];
		WorkerThread[] workers = new WorkerThread[node];
		
		for(int p=0; p<node; p++){
			workers[p] = new WorkerThread();
		}
		
		for(int i=0; i<node; i++){
			aclient[i] = new AClient(ssdp_port, eventsub_port,"secret\\m"+fileNumber+".txt");
			ssdp_port ++;
			aclient[i].start();
			
			//detect device
			boolean searchGC = true;
			boolean foundDev1 = false;
			boolean foundDev2 = false;
			boolean foundDev3 = false;
			boolean foundDev4 = false;
			while(searchGC){
				Device dev1 = aclient[i].getDevice(CLOCK_DEVICE_TYPE);
				Device dev2 = aclient[i].getDevice(TV_DEVICE_TYPE);
				Device dev3 = aclient[i].getDevice(WASHER_DEVICE_TYPE);
				Device dev4 = aclient[i].getDevice(AIRCON_DEVICE_TYPE);
				
				if(dev1 == null){
					System.err.println("Clock not found");
				}else{
					System.err.println("Clock found");
					foundDev1 = true;
				}
				
				if(dev2 == null){
					System.err.println("TV not found");
				}else{
					System.err.println("TV found");
					foundDev2 = true;
				}
				
				if(dev3 == null){
					System.err.println("Washer not found");
				}else{
					System.err.println("Washer found");
					foundDev3 = true;
				}
				
				if(dev4 == null){
					System.err.println("Aircon not found");
				}else{
					System.err.println("Aircon found");
					foundDev4 = true;
				}
				
				
				if(!foundDev1 || !foundDev2 || !foundDev3 || !foundDev4)
				{
					System.err.println("retry...");
					aclient[i].search();
					try{
						Thread.sleep(5000);
					}catch(Exception e){}
				}else{
					searchGC = false;
				}
			}
			workers[i].setA(aclient[i]);
			fileNumber++;
		}
		
		
		//#1 sequence node start
		int rekeyFix = 0; //wait longer when node greater
		for(int j=0; j<node; j++){

			workers[j].start(); //****authen
			
			try{
				Thread.sleep(10000+rekeyFix);
				
			}catch(Exception e){
				System.exit(0);
			}
			
			rekeyFix += 250;
		}
		*/
				
		//#2 random node start
		/*
		Random r = new Random();
		long waitTime =0;
		int maxWaitTime = 15000* node;
		for (int j=0; j<node; j++){
			waitTime = r.nextInt(maxWaitTime);
			if(0> waitTime)
				waitTime *= -1;
			workers[j].setStartTime(waitTime);
			
			System.err.println(workers[j].getName()+" start in "+waitTime+" ms");
		}
		
		//run test
		for(int k=0; k<node; k++){
			workers[k].start();
		}
		*/

		//1. test node leave
		//workers[0].leaveGroup();
		
		//2. test response time unicast (secured getClockTime)
		/*
		for(int ii=0; ii<node; ii++){
			workers[ii].securedGetClockTime();
			try{
				Thread.sleep(5000);
			}catch(Exception e){}
		}
		*/
		/*
		for(int ii=0; ii<node; ii++){
			workers[ii].securedGetClockTime();
			try{
				Thread.sleep(5000);
			}catch(Exception e){}
		}
		*/
		/*
		String startTime = AClient.getCurrentTime();
		for(int i=0; i< 500; i++){
			workers[0].securedGetClockTime();
			
			try{
				Thread.sleep(5000);
			}catch(Exception e){}
			
			//workers[1].setAVPowerOn(false);
		}
		String finishTime = AClient.getCurrentTime();
		System.err.println("[AClient]Start time ="+startTime
				+" finishTime ="+finishTime);
		*/	
		
		//System.exit(0);
	}
	
	/*
	 * FOR TESTING PURPOSE
	 */
	public void printESPTime(long elapsedTime, String fileName, String worker)
	{
		
		//long espTime = finishTime - startTime;
		//System.err.println(Thread.currentThread().getName()+" esp time "+espTime);
		String espStr = worker+" "+elapsedTime+ HTTP.CRLF;
		//String espStr = elapsedTime+ HTTP.CRLF;
		try{
			FileOutputStream f_out = new FileOutputStream(fileName,true);
			f_out.write(espStr.getBytes());
			f_out.close();
		}catch(Exception e){}
	}
	
	public void securedGetClockTime(){

		if(this.groupControllerDevice == null){
			//System.out.println("GET TIME:NO DEVICE");
			return;
		}
		
		Action getTimeAct = groupControllerDevice.getAction("GetTime");
		
		secureSoapExecution(getTimeAct);
		
		
		ArgumentList outArgList = getTimeAct.getOutputArgumentList();
		String time = outArgList.getArgument(0).getValue();
		
		//System.out.println(this.deviceName+":[secured]Time is "+time);
	}
	
	public void startAVReceiever(boolean poweron){

		
		if(this.groupControllerDevice == null){
			System.err.println("LIVE TV:NO DEVICE");
			return;
		}
		
		Action getBroadcastVideoURI = groupControllerDevice.getAction("GetBroadcastVideoURI");
		
		
		if(getBroadcastVideoURI.postControlAction() == false){ //getAddress
			return;
		}
		
		String broadcastVideoURI = getBroadcastVideoURI.getArgumentValue("BroadcastVideoURI");
		
		
		Action getBroadcastAudioURI = groupControllerDevice.getAction("GetBroadcastAudioURI");
		
		if (getBroadcastAudioURI.postControlAction() == false){ //getPort
			return;
		}
		
		String broadAudioURI = getBroadcastAudioURI.getArgumentValue("BroadcastAudioURI");
		
		if(poweron){
			Action tvPowerOn = groupControllerDevice.getAction("TVPowerOn");
			
			if (tvPowerOn.postControlAction() == false){ //getPort
				return;
			}
		}else{
			System.err.println("Power off<<<<<<<<<<<");
		}
		
		
		

		String[] args = {broadcastVideoURI, broadAudioURI};
		
		byte[] groupKey = this.groupKey;  //argv 2
		
		System.out.println(deviceName+":started AV receiver");
	
		//#######on off group security here!!!!!
		AVReceive3 avReceive = new AVReceive3(args, groupKey/*null*/);
		//AVReceive3 avReceive = new AVReceive3(args, /*groupKey*/null);
		String startTime = getCurrentTime();
		if(!avReceive.initialize()){
			System.err.println("Failed to initialize the sessions.");
			return;
		}
		
		try{
			while(!avReceive.isDone())
				Thread.sleep(1000);
		}catch(Exception e){}
		String finishTime = getCurrentTime();
		
		//get total package count
		double pkgCnt = avReceive.getPkgCount();
		
		//debug
		System.err.println(Thread.currentThread().getName()+" total package count "+pkgCnt);
		System.err.println("AVReceive start time "+startTime+" finish time "+finishTime);
		
		

	}
	
	public static String getCurrentTime(){
		Calendar cal = Calendar.getInstance();
		String currentTime = cal.get(Calendar.HOUR)+":";
		currentTime += cal.get(Calendar.MINUTE)+ ".";
		currentTime += cal.get(Calendar.SECOND);
		
		return currentTime;
	}
	
	/*
	 * Todo note:
	 * at Client CHOOSING group controller (gc) algorithm
	 * 1. start upnp server
	 * 2. search device
	 * 3. [auto] search best group controller
	 * 3.0 for each device has deviceSecurity service [ifnot reject that device]
	 * 3.1 for each device same groupID as client [ifnot reject that device]
	 * 3.2 for each has same groupID CHOOSE biggest groupSize [if no bigest goto next]
	 * 3.3 for each has fastest CPUSpeed [if no fastest goto next]
	 * 3.4 for earch has bigest FreeMemory (should getSome or return not found best gc)
	 */
	
	private static final String DEVICE_SECURITY_SERVICE = "urn:schemas-upnp-org:service:devicesecurity:1";
	
	boolean groupControllerFound = false;
	public void printSecureSupportedDevice()
	{
		DeviceList devList = getSecureDeviceList();
		int nSize = devList.size();
		System.out.println("Secure device found "+nSize);
		
		Device currentDev = null;
		if(nSize == 0){
			System.out.println("No secure device found");
			try{
				Thread.sleep(10000);
			}catch(Exception e){
				
			}
			return;
		}
		for(int i=0; i<nSize; i++)
		{
			currentDev = devList.getDevice(i);
			String name = currentDev.getFriendlyName();
			System.out.println("Friendly name = "+name);
			System.out.println("Groupid="+currentDev.getSecureGroupID()+" gc="+currentDev.isGroupController());
		}
		
		
		//if found only gc
		/*
		if(nSize == 1){
			boolean isGroupController = currentDev.isGroupController();
			String secureGroupID = currentDev.getSecureGroupID();
			
			if(isGroupController && secureGroupID.equals("uuid:jakapan1234")){
				System.out.println("GC found! Joining...");
				setGroupControllerDevice(currentDev);
				boolean rt = startKPSMode();
				
				if(rt)
					this.setStatus(AClient.STATUS_REGISTERED);
			}else{
				System.out.println("Not a Group Controller!");
			}
		}
		*/
		
		//here only one gc is fixed
		boolean isGroupController = currentDev.isGroupController();
		String secureGroupID = currentDev.getSecureGroupID();
		
		if(isGroupController && secureGroupID.equals("uuid:jakapan1234")){
			System.out.println("GC found! Joining...");
			setGroupControllerDevice(currentDev);
			boolean rt = startKPSMode();
			
			if(rt)
				this.setStatus(AClient.STATUS_REGISTERED);
		}else{
			System.out.println("Not a Group Controller!");
			try{
				Thread.sleep(10000);
			}catch(Exception e){
				
			}
		}
		
	}
	
	public void printSecureSupportedDevicePKI()
	{
		DeviceList devList = getSecureDeviceList();
		int nSize = devList.size();
		
		Device currentDev = null;
		if(nSize == 0){
			System.out.println("no secure device found");
			return;
		}
		for(int i=0; i<nSize; i++)
		{
			currentDev = devList.getDevice(i);
			String name = currentDev.getFriendlyName();
			System.out.println("Secure device "+i+" friendly name = "+name);
		}
		
		
		//if found only gc
		if(nSize == 1){
			boolean isGroupController = currentDev.isGroupController();
			String secureGroupID = currentDev.getSecureGroupID();
			
			if(isGroupController && secureGroupID.equals("uuid:jakapan1234")){
				//System.out.println("GC found! Joining...");
				setGroupControllerDevice(currentDev);
				//boolean rt = startKPSMode();
				boolean rt = startPKIMode();
				
				if(rt)
					this.setStatus(AClient.STATUS_REGISTERED);
			}
		}
		
	}
	
	
	public Device chooseGroupController()
	{
		DeviceList devList = getDeviceList();
		int nSize = devList.size();
		
		DeviceList tmpDevList = new DeviceList();
		Device currentDev = null;
		
		System.out.println("ControlPoint: Device found="+nSize);
		
		//3.0 check if device support security service
		for(int i=0; i<nSize; i++){
			currentDev = devList.getDevice(i);

			if(currentDev == null){
				System.err.println("[Null] device");
				return null;
			}
			
			Service securityService = currentDev.getService(DEVICE_SECURITY_SERVICE);
			
			if(securityService == null){
				System.err.println("Device"+i+": Device is not support security service");
				continue;
			}else{
				System.out.println("Device"+i+": Security service supported");

				//add to next qualify
				tmpDevList.add(currentDev);
			}
			
		
		}
		
		
		
		
		
		//3.1 check if device is in the same group
		
		GroupControllerList gcList = new GroupControllerList();
		System.out.println("Device \tGroupID \tGroupSize \tCPUSpeed \tFreeMem \tSameGroupID \tFriendly");
		for(int i=0; i< tmpDevList.size(); i++){
			// get group controller score
			currentDev = tmpDevList.getDevice(i);
			
			Action getGroupControllerScoreAction = currentDev.getAction("GetGroupControllerScore");
			if(getGroupControllerScoreAction.postControlAction() == false){
				System.err.println("Device"+i+": Error get group controller score");
				continue;
			}
			
			String groupID = getGroupControllerScoreAction.getArgumentValue("GroupID");
			String groupSize = getGroupControllerScoreAction.getArgumentValue("GroupSize");
			String cpuSpeed = getGroupControllerScoreAction.getArgumentValue("CPUSpeed");
			String freeMem = getGroupControllerScoreAction.getArgumentValue("FreeMemory");
			String friendly = currentDev.getFriendlyName();
			
			
			System.out.print(i);
			System.out.print("\t"+groupID);
			System.out.print("\t\t"+groupSize);
			System.out.print("\t\t"+cpuSpeed);
			System.out.print("\t\t"+freeMem);
			
			
			int cpuSpeedInt = Integer.parseInt(cpuSpeed);
			int groupSizeInt = Integer.parseInt(groupSize);
			long freeMemL = Long.parseLong(freeMem);
			
			if(groupID.equals(GROUP_ID)){
				GroupController gc = new GroupController(currentDev);
				gc.setGroupID(groupID);
				gc.setGroupSize(groupSizeInt);
				gc.setCpuSpeedMHz(cpuSpeedInt);
				gc.setFreeMemoryKB(freeMemL);
				
				gcList.add(gc);
				
				System.out.print("\t\tYes");
			}else{
				//dont care
				System.out.print("\t\tNo");
			}
			
			System.out.println("\t\t"+friendly);
		}
		
		//3.2 choose, if there is the biggest choose bigest group size
		GroupControllerList biggestGroupList = new GroupControllerList();
		int biggestCnt = gcList.getBiggest(biggestGroupList);
		
		if(biggestCnt == 1){
			return biggestGroupList.getDevice(0);
		}
		
		//3.3 choose, if there is the fastest speed choose
		GroupControllerList fastestCPUList = new GroupControllerList();
		int fastestCpuCnt = biggestGroupList.getFastestCPU(fastestCPUList);
		
		if(fastestCpuCnt ==1){
			return fastestCPUList.getDevice(0);
		}
		
		
		
		//3.4 choose, if there is the bigest free mem space
		GroupControllerList biggestFreemem = new GroupControllerList();
		int biggestFreememCnt = fastestCPUList.getBiggestFreeMem(biggestFreemem);
		
		if(biggestFreememCnt == 1){
			return biggestFreemem.getDevice(0);
		}
		
		return null; //can not choose
	}
	
	public String getDeviceName(){
		return this.deviceName;
	}

}
